﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Configuration;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using PubSubLite.Configuration;
using PubSubLite.Utils;
using System.Text.RegularExpressions;

namespace PubSubLite.Messaging.MessageChannels
{
    public class TcpMessageChannel : IMessageChannel
    {
        public TcpMessageChannel(IBusConfiguration config, TcpChannelConfigElement configElement)
        {
            Require.Argument.IsNotNull(config, "config");
            Require.Argument.IsNotNull(config, "configElement");
            _id = Guid.NewGuid();
            _configuration = config;
            _configElement = configElement;
            _port = configElement.HostPort;
            _clientPort = configElement.ClientPort;
            var client = Dns.GetHostEntry("localhost");
            var ipAddress = client.AddressList[0];
            _hostSocket = new TcpListener(ipAddress, configElement.HostPort);
            _isRunning = true;
            _hostSocket.Start();
            _hostThread = new Thread(BeginListen);
            _hostThread.Start();

        }

        private Guid _id;
        private int _port;
        private int _clientPort;
        private bool _isRunning;
        private TcpListener _hostSocket;
        private Thread _hostThread;
        private IBusConfiguration _configuration;
        private TcpChannelConfigElement _configElement;

        Guid IMessageChannel.ID
        {
            get { return _id; }
        }

        void IMessageChannel.Send(IMessageBase message)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(TrySend), message);
        }

        private event EventHandler<MessageReceivedEventArgs> _messageReceived;

        event EventHandler<MessageReceivedEventArgs> IMessageChannel.MessageReceived
        {
            add { _messageReceived += value; }
            remove { _messageReceived -= value; }
        }

        private void OnMessageReceived(IMessageBase message)
        {
            _configuration.Debug("SocketMessageChannel message received " + message.MessageID.ToString());
            if (_messageReceived != null)
            {
                _messageReceived(this, new MessageReceivedEventArgs(message));
            }
        }

        private void BeginListen()
        {
            _configuration.Debug("SocketMessageChannel start listening...");
            while (_isRunning)
            {
                var client = _hostSocket.AcceptTcpClient();
                _configuration.Debug("SocketMessageChannel client accepted");
                var stream = client.GetStream();
                var formatter = new BinaryFormatter();
                var mess = formatter.Deserialize(stream);
                stream.Close();
                client.Close();
                OnMessageReceived((IMessageBase)mess);
            }
        }

        private void TrySend(object messageObj)
        {
            var message = (IMessageBase)messageObj;
            _configuration.Debug("SocketMessageChannel - sending msg " + message.MessageID.ToString());
            var success = false;
            DateTime startTime = DateTime.Now;
            // try send the message for 15 seconds, then ignore
            while (_isRunning && !success && DateTime.Now < startTime.AddSeconds(15))
            {
                try
                {
                    var client = new TcpClient(_configElement.ClientUri, _clientPort);
                    var stream = client.GetStream();
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(stream, message);
                    stream.Close();
                    client.Close();
                    success = true;
                }
                catch (Exception e)
                {
                    _configuration.LogWarning(string.Format("Could not send to client {0}:{1} Exception: {2}", _configElement.ClientUri, _configElement.ClientPort, e.Message));
                    Thread.Sleep(1000);
                }
            }
        }
    }
}
